package com.pxmeta.dynamic.service.impl;

import com.pxmeta.dynamic.service.RedisLockService;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class RedisLockServiceImpl implements RedisLockService {

    @Autowired
    public RedisTemplate redisTemplate;
    //    解锁原子性操作脚本
    public static final String unlockScript="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
            + "then\n"
            + "    return redis.call(\"del\",KEYS[1])\n"
            + "else\n"
            + "    return 0\n"
            + "end";

    /**
     * 加锁，有阻塞
     * @param name
     * @param expire
     * @param timeout
     * @return
     */
    @Override
    @SneakyThrows
    public String lock(String name, long expire, long timeout) {
        long startTime=System.currentTimeMillis();
        String token;
        do{
            token=tryLock(name,expire);
            if(token==null){
                //设置等待时间，若等待时间过长则获取锁失败
                if((System.currentTimeMillis()-startTime)>(timeout-50)){
                    break;
                }
                try {
                    Thread.sleep(50);//try it again per 50
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }while (token==null);
        return token;
    }

    /**
     * 解锁
     * @param name
     * @param token
     * @return
     */
    @Override
    @SneakyThrows
    public Boolean unlock(String name, String token) {
        byte[][] keyArgs=new byte[2][];
        keyArgs[0]= name.getBytes(Charset.forName("UTF-8"));
        keyArgs[1]= token.getBytes(Charset.forName("UTF-8"));
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try{
            Long result = connection.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keyArgs);
            if(result!=null&&result>0){
                return true;
            }

        }finally {
            RedisConnectionUtils.releaseConnection(connection,connectionFactory);
        }
        return false;
    }

    /**
     * 加锁，无阻塞
     * @param name
     * @param expire
     * @return
     */
    @Override
    @SneakyThrows
    public String tryLock(String name, long expire) {
        String token= UUID.randomUUID().toString();
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try{
            Boolean result = connection.set(name.getBytes(Charset.forName("UTF-8")), token.getBytes(Charset.forName("UTF-8")),
                    Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
            if(result!=null&&result){
                return token;
            }
        }
        finally {
            RedisConnectionUtils.releaseConnection(connection,connectionFactory);
        }
        return null;
    }
}
